assert(PandaDKP, "PandaDKP not found!")

local kLootStateNotOpen = 0
local kLootStateOpen = 1
local kLootStateClosing = 2
local kLootStateClosed = 3
local kLootStateAnnounced = 4

------------------------------
--      Are you local?      --
------------------------------
local PandaDKP = PandaDKP
local Looting =
{
   Buttons = {},
   Items = {},
   LootState = kLootStateNotOpen,
   CloseTime = 30,
   Options = {},
   LootingOn = false,
}
PandaDKP.Looting = Looting

local dewdrop = AceLibrary("Dewdrop-2.5")
local dewOptions = {}



function Looting:Enable()
   if (PandaDKP.db.realm.LootScanning) then Looting:TurnOnLootChecking() end
end

function Looting:TurnOnLootChecking()
   if (not Looting.LootingOn) then
      PandaDKP:RegisterEvent("LOOT_OPENED", function() PandaDKP.Looting:OnLootOpened() end)
      Looting.LootingOn = true
   end
end

function Looting:TurnOffLootChecking()
   if (Looting.LootingOn) then
      PandaDKP:UnregisterEvent("LOOT_OPENED")
      Looting.LootingOn = false
   end
end


function Looting:ShowLootDistribution()
   -- Calculate the height of the window based on the items shown
   local height = 55 + (50 * #(Looting.Items))
   PDKPM_Loot:SetHeight(height)
   
   PDKPM_Loot:Show()
end


function Looting:ShowActionsMenu()

   dewOptions = { type = "group", args = {} }
   dewOptions.args.header = { type = 'header', name = 'Actions', order = 1 } 
   dewOptions.args.clearall = { type = 'execute', name = 'Clear all', 
      desc = 'Clear all items from the distribution window',
      func = function() PandaDKP.Looting:ClearAll() dewdrop:Close() end, order = 100 }
   
   if (kLootStateNotOpen == Looting.LootState) then
      dewOptions.args.openbids = Looting.Options.openbids
   elseif (kLootStateOpen == Looting.LootState) then
      dewOptions.args.closetimer = Looting.Options.closetimer
      dewOptions.args.closebidding = Looting.Options.closebidding
      dewOptions.args.cancelbidding = Looting.Options.cancelbidding
   elseif (kLootStateClosing == Looting.LootState) then
      dewOptions.args.canceltimer = Looting.Options.canceltimer
      dewOptions.args.cancelbidding = Looting.Options.cancelbidding
   elseif (kLootStateClosed == Looting.LootState) then
      dewOptions.args.openbids = Looting.Options.openbids
      dewOptions.args.announce = Looting.Options.announce
   elseif (kLootStateAnnounced == Looting.LootState) then
      dewOptions.args.openbids = Looting.Options.openbids
      dewOptions.args.announce = Looting.Options.announce
      dewOptions.args.deduct = Looting.Options.deduct
   end

   dewdrop:Open(PDKPM_LootActionsButton, 'children', dewOptions, 'point', "TOP", 'relativePoint', "TOP")
end


function Looting:OnLootOpened()
   local numAdded = 0
   
   -- If we're already in a bidding phase, ignore this
   if (Looting.LootState > kLootStateNotOpen) then return end
   
   -- Get the name of who we looted
   local mobname = UnitName("target")
   if mobname == nil then mobname = "Unknown mob" end
   
   -- Process every item link on the corpse.
   local slot, link, numSlots
   numSlots = GetNumLootItems()
   if numSlots == 0 then return end
   
   for slot = 1, numSlots, 1 do
      link = GetLootSlotLink(slot)
      if link ~= nil then -- Coins come out as a nil link
         if Looting:ProcessItemLink(link, mobname) then
            numAdded = numAdded + 1
         end
      end
   end
   
   -- Show the loot distribution window if we added something to the list
   if numAdded > 0 then Looting:ShowLootDistribution() end
end


function Looting:ProcessItemLink(link, source)
   
   local name, _, quality, _, _,type, subtype, stackcount, _, texture = GetItemInfo(link)
   if name == nil then
      PandaDKP:Print(string.format("Invalid item link: %s", link))
      return false
   end
   
   local zone = GetZoneText()
   local iteminfo = { 
      name = name, 
      link = link,
      itemId = PandaDKP:ExtractItemID(link),
      class = type, 
      subclass = subtype, 
      quality = quality, 
      colour = PandaDKP:rgbToHex(ITEM_QUALITY_COLORS[quality]),
      colourrgb = { r=ITEM_QUALITY_COLORS[quality].r, g=ITEM_QUALITY_COLORS[quality].g, b=ITEM_QUALITY_COLORS[quality].b }, 
      count = stackcount, 
      texture = texture, 
      source = source,
      zone = zone,
      time = date("%m/%d/%y %H:%M:%S"),
      bidinfo = { Winner = "Disenchant", NeedBidders = {}, GreedBidders = {}, IsGreed = false, },
   }

   local added = false
   if (PandaDKP.Looting:ShouldIncludeItem(iteminfo.itemId)) then
      Looting:AddItem(iteminfo)
      added = true
   end
   
   return added
end
   

function Looting:ShouldIncludeItem(itemId)
   -- If item meets threshold, return true
   local _, _, quality = GetItemInfo(tostring(itemId))
   if (quality >= PandaDKP.db.realm.ItemQuality) then return true end
   
   return false
end



function Looting:OpenBidding()
   -- Make sure we're in a raid
   if GetNumRaidMembers() == 0 then
      PandaDKP:Print("You're not in a raid group.")
      return
   end
   
   -- Announce the items to raid chat
   SendChatMessage("Bidding is now open on:", "RAID")
   for i = 1, #(Looting.Items) do
      SendChatMessage(string.format("   %s", Looting.Items[i].link), "RAID")
   end
   
   -- Send an 'OPEN' message for each item in the list
   for i = 1, #(Looting.Items) do
      local message = string.format('OPEN %s', Looting.Items[i].link)
      PandaDKP.Comm:Send("RAID", message)
   end
   
   -- Set the state to open
   Looting.LootState = kLootStateOpen
end

function Looting:CloseBidding()
   -- Start a timer to close bidding
   Looting.CloseTimeRemaining = Looting.CloseTime + 1
   Looting.CloseTimer = PandaDKP:ScheduleRepeatingTimer(function() PandaDKP.Looting:CloseTimerFired() end, 1)
   Looting.LootState = kLootStateClosing
end

function Looting:CloseTimerFired()
   Looting.CloseTimeRemaining = Looting.CloseTimeRemaining - 1
   local remaining = Looting.CloseTimeRemaining
   if (remaining <= 0) then
      -- We're done
      Looting:BiddingClosed()
      PandaDKP:CancelTimer(Looting.CloseTimer)
      Looting.CloseTimer = nil
   elseif (0 == remaining % 10 or remaining <= 5) then
      if IsRaidOfficer() then
         SendChatMessage(string.format("Bidding closing in %g seconds.", remaining), "RAID_WARNING")
      else
         SendChatMessage(string.format("Bidding closing in %g seconds.", remaining), "RAID")
      end
   end
end

function Looting:BiddingClosed()
   Looting.LootState = kLootStateClosed
   PandaDKP.Comm:Send("RAID", "CLOSED")
end

function Looting:CancelTimer()
   Looting.LootState = kLootStateOpen
   PandaDKP:CancelTimer(Looting.CloseTimer)
   Looting.CloseTimer = nil
   SendChatMessage("Bidding temporarily cancelled. Your bids are saved.", "RAID")
end

function Looting:CancelBidding()
   Looting.LootState = kLootStateNotOpen
   if (nil ~= Looting.CloseTimer) then
      PandaDKP:CancelTimer(Looting.CloseTimer)
      Looting.CloseTimer = nil
   end
   SendChatMessage("Bidding cancelled. You will need to re-bid.", "RAID")
   PandaDKP.Comm:Send("RAID", "CANCEL")
end

function Looting:AnnounceWinners()
   SendChatMessage("And the winners are:", "RAID")
   local i
   for i = 1, #(Looting.Items) do
      local ilink, winner = Looting.Items[i].link, Looting.Items[i].bidinfo.Winner
      local need = Looting.Items[i].bidinfo.IsGreed and "Greed" or "Need"
      SendChatMessage(string.format("%s -- %s (%s)", ilink, winner, need), "RAID")
   end
   Looting.LootState = kLootStateNotOpen
end

function Looting:RemoveItem(id)
   assert(id > 0 and id <= #(Looting.Items))
   tremove(Looting.Items, id)
   for i = id, #(Looting.Buttons) do
      if (i <= #(Looting.Items)) then
         Looting:SetButtonInfo(Looting.Buttons[i], Looting.Items[i])
      else
         Looting.Buttons[i]:Hide()
      end
   end
   
   -- Show the window again to recalculate the window size
   Looting:ShowLootDistribution()
end

function Looting:ClearAll()
   -- If we're in any part of the bidding process, we need to tell people
   if (kLootStateNotOpen ~= Looting.LootState) then Looting:CancelBidding() end
   
   -- Empty the item list and hide the buttons on the interface
   Looting.Items = {}
   for i = 1, #(Looting.Buttons) do
      Looting.Buttons[i]:Hide()
   end
   
   -- Show the window again to recalculate the window size
   Looting:ShowLootDistribution()
end

function Looting:AddRemoveBidder(id, bidder, greed)
   if (nil ~= Looting.Items[id]) then
      if (greed) then
         if (nil == Looting.Items[id].bidinfo.GreedBidders[bidder]) then
            Looting.Items[id].bidinfo.GreedBidders[bidder] = true
            Looting.Items[id].bidinfo.NeedBidders[bidder] = nil
         else
            Looting.Items[id].bidinfo.GreedBidders[bidder] = nil
         end
      else
         if (nil == Looting.Items[id].bidinfo.NeedBidders[bidder]) then
            Looting.Items[id].bidinfo.NeedBidders[bidder] = true
            Looting.Items[id].bidinfo.GreedBidders[bidder] = nil
         else
            Looting.Items[id].bidinfo.NeedBidders[bidder] = nil
         end
      end
   end
end

function Looting:BidReceived(bidder, itemId, state)
   local found = false
   for i=1, #(Looting.Items) do
      if (Looting.Items[i].itemId == itemId) then
         if ('N' == state) then
            if (nil == Looting.Items[i].bidinfo.NeedBidders[bidder]) then
               Looting.Items[i].bidinfo.NeedBidders[bidder] = true
               Looting.Items[i].bidinfo.GreedBidders[bidder] = nil
            end
         elseif ('G' == state) then
            if (nil == Looting.Items[i].bidinfo.GreedBidders[bidder]) then
               Looting.Items[i].bidinfo.GreedBidders[bidder] = true
               Looting.Items[i].bidinfo.NeedBidders[bidder] = nil
            end
         elseif ('U' == state) then
            Looting.Items[i].bidinfo.NeedBidders[bidder] = nil
            Looting.Items[i].bidinfo.GreedBidders[bidder] = nil
         end
         Looting:UpdateStatusText(i)
         found = true
      end
   end
   if found then PandaDKP.Comm:Send("WHISPER", string.format("ACCEPTED %s %s", itemId, state), bidder) end
end

function Looting:SetWinner(id, winner, greed)
   if (nil == greed) then greed = false end
   if (nil ~= Looting.Items[id]) then
      Looting.Items[id].bidinfo.Winner = winner
      Looting.Items[id].bidinfo.IsGreed = greed
      Looting:UpdateStatusText(id)
   end
end

function Looting:RaidMembers(id)
   -- Get a list of all players in the raid
   local members = {}
   for i=1, GetNumRaidMembers() do
      local name, rank, subgroup, level, class = GetRaidRosterInfo(i)
      members[name] = class
   end
   
   -- Sort the players by name
   local sorted = {}
   for i, v in pairs(members) do
      tinsert(sorted, { name = i, class = v })
   end
   sort(sorted, function(a,b) return a.name < b.name end)
   
   return sorted
end




function Looting:AddItem(info)
   -- Add the item to the list of items we're looting
   tinsert(Looting.Items, info)
   
   -- Get a reference to the GUI button that will display our item
   local button
   if (#(Looting.Buttons) < #(Looting.Items)) then
      -- Create a new loot button for the item
      button = Looting:CreateButton(#(Looting.Items))
   else
      -- We can re-use an existing button
      button = Looting.Buttons[#(Looting.Items)]
   end
   
   -- Set the button's item information
   Looting:SetButtonInfo(button, info)
end

function Looting:CreateButton(id)
   local button = CreateFrame("Button", "PDKPM_LootButton"..id, PDKPM_Loot, "PDKPM_LootButtonTemplate")
   button:SetID(id)
   
   button:ClearAllPoints()
   button:SetPoint("TOPLEFT", PDKPM_Loot, "TOPLEFT", 10, -50 - (id-1)*(button:GetHeight()+ 5))
   
   button.lootButton = _G["PDKPM_LootButton"..id.."LootButton"]
   button.itemname = _G["PDKPM_LootButton"..id.."Text"]
   button.statusline = _G["PDKPM_LootButton"..id.."Description"]
   
   Looting.Buttons[id] = button
   return button
end

function Looting:SetButtonInfo(button, info)
   button.lootButton:SetNormalTexture(info.texture)
   button.itemname:SetTextColor(info.colourrgb.r, info.colourrgb.g, info.colourrgb.b)
   button.itemname:SetText(info.name)
   Looting:UpdateStatusText(button:GetID())
   button:Show()
end

function Looting:ShowTooltip(button, id)
   GameTooltip:SetOwner(button, "ANCHOR_TOPLEFT")
   GameTooltip:SetHyperlink(Looting.Items[id].link)
   GameTooltip:Show()
   if IsShiftKeyDown() then GameTooltip_ShowCompareItem() end
   CursorUpdate()
end

function Looting:UpdateStatusText(id)
   local button = Looting.Buttons[id]
   local bidinfo = Looting.Items[id].bidinfo
   
   local count = 0
   for i,v in pairs(bidinfo.NeedBidders) do count = count + 1 end
   for i,v in pairs(bidinfo.GreedBidders) do count = count + 1 end
   
   local winnerName = bidinfo.Winner
   local need = bidinfo.IsGreed and "Greed" or "Need"
   local hex
   local class = EPGP:GetClass(winnerName)
   if (nil ~= class) then hex = PandaDKP:getClassHex(class) end
   local coloredName = hex and string.format("|cff%s%s|r (%s)",hex, winnerName, need) or winnerName
   button.statusline:SetText(string.format("%d Bids  -- Winner = %s", count, coloredName))
end

function Looting:OnLootButtonClick(button, mouseButton, id)
   local i
   
   -- If its a ctrl-click, then show the item in dress up
   if (mouseButton == "LeftButton" and IsControlKeyDown()) then
      DressUpItemLink(Looting.Items[id].link)
      return
   elseif (mouseButton == "LeftButton" and IsShiftKeyDown()) then
      ChatEdit_InsertLink(Looting.Items[id].link)
      return
   end
   
   -- Build the menu for this item
   dewOptions = { type = "group", args = {} }
   if ("LeftButton" == mouseButton) then
      dewOptions.args.header = { type = 'header', name = Looting.Items[id].link, order = 1 }
      dewOptions.args.disenchantit = Looting.Options.disenchantit
      dewOptions.args.disenchantit.passValue = id
      dewOptions.args.guildbankit = Looting.Options.guildbankit
      dewOptions.args.guildbankit.passValue = id
      
      local sorted = {}
      
      -- Add all Need bidders
      dewOptions.args.nspacer = { type = 'header', name = ' ', order = 9 }
      dewOptions.args.nheader = { type = 'header', name = 'Need', order = 10 }
      for player, value in pairs(Looting.Items[id].bidinfo.NeedBidders) do
         local class = EPGP:GetClass(player)
         local ep, gp, main = EPGP:GetEPGP(player)
         local playerInfo = { type = 'execute',
            name = string.format("|cff%s%s|r (%g)", PandaDKP:getClassHex(class), player, ep / gp),
            order = 11,
            desc = string.format("Set the winner to %s", player),
	         func = function(v) PandaDKP.Looting:SetWinner(v.id, v.player) end,
	         passValue = { id = id, player = player }, }
	      dewOptions.args['n_'..player] = playerInfo
         tinsert(sorted, { name = player, lp = ep / gp })
      end
   
      -- Sort the players by LP and set their sort order accordingly
      sort(sorted, function(a,b) return a.lp > b.lp end)
      for index, value in ipairs(sorted) do
         dewOptions.args['n_'.. value.name].order = 10 + index
      end
      sorted = {}
      
      
      -- Add all Greed bidders
      dewOptions.args.gspacer = { type = 'header', name = ' ', order = 49 }
      dewOptions.args.gheader = { type = 'header', name = 'Greed', order = 50 }
      for player, value in pairs(Looting.Items[id].bidinfo.GreedBidders) do
         local class = EPGP:GetClass(player)
         local ep, gp, main = EPGP:GetEPGP(player)
         local playerInfo = { type = 'execute',
            name = string.format("|cff%s%s|r (%g)", PandaDKP:getClassHex(class), player, ep / gp),
            order = 51,
            desc = string.format("Set the winner to %s", player),
	         func = function(v) PandaDKP.Looting:SetWinner(v.id, v.player, true) end,
	         passValue = { id = id, player = player }, }
	      dewOptions.args['g_'..player] = playerInfo
         tinsert(sorted, { name = player, lp = ep / gp })
      end
   
      -- Sort the players by LP and set their sort order accordingly
      sort(sorted, function(a,b) return a.lp > b.lp end)
      for index, value in ipairs(sorted) do
         dewOptions.args['g_'.. value.name].order = 50 + index
      end
      sorted = {}
      
      dewOptions.args.spacer = { type = 'header', name = ' ', order = 100 }
      dewdrop:Open(button, 'children', dewOptions, 'point', "TOP", 'relativePoint', "TOP")
      return
   elseif ("RightButton" == mouseButton) then
      local showMenu = false
      if (kLootStateNotOpen == Looting.LootState) then
         dewOptions.args.removeitem = Looting.Options.removeitem
         dewOptions.args.removeitem.passValue = { id = id, link = Looting.Items[id].link }
         showMenu = true
      end
      
      -- Master Looter
      
      -- If we've opened bidding, but not yet deducted points, allow a player's name to be added
      if (Looting.LootState > kLootStateNotOpen) then
         -- Add places for the Need and Greed groups
         dewOptions.args.need = { type = "group", args = {}, name = 'Add/remove NEED bidder', desc = 'Add, or remove, a player from the list of need bidders for this item.', order = 4 }
         dewOptions.args.greed = { type = "group", args = {}, name = 'Add/remove GREED bidder', desc = 'Add, or remove, a player from the list of greed bidders for this item.', order = 5 }
         
         local players = Looting:RaidMembers(id)
         local player
         for i, player in pairs(players) do
            local hex = PandaDKP:getClassHex(player.class)
            dewOptions.args.need.args["p"..i] = {
               type = 'execute',
               name = string.format("|cff%s%s|r ", hex, player.name),
               desc = string.format("Add %s to the need list", player.name),
               func = function(data) PandaDKP.Looting:AddRemoveBidder(data.id, data.player, false) end,
               passValue = { id = id, player = player.name },
            }
            dewOptions.args.greed.args["p"..i] = {
               type = 'execute',
               name = string.format("|cff%s%s|r ", hex, player.name),
               desc = string.format("Add %s to the greed list", player.name),
               func = function(data) PandaDKP.Looting:AddRemoveBidder(data.id, data.player, true) end,
               passValue = { id = id, player = player.name },
            }
         end
         showMenu = true
      end
      
      if (showMenu) then
         dewdrop:Open(button, 'children', dewOptions, 'point', "TOP", 'relativePoint', "TOP")
      end
   end
end


Looting.Options =
{
   openbids = { type = 'execute', name = 'Open bidding', desc = 'Open bidding on all items',
	 func = function() PandaDKP.Looting:OpenBidding(); dewdrop:Close() end, order = 2 },
	 
	closetimer = { type = 'range', name = 'Close timer', desc = 'Select time in which to close bidding',
	 min = 5, max = 60, step = 1,
	 get = function() return PandaDKP.Looting.CloseTime end,
	 set = function(v) PandaDKP.Looting.CloseTime = v end,
	 order = 3 },
	
	closebidding = { type = 'execute', name = 'Close bidding', desc = 'Start the timer to close bidding on all items',
	 func = function() PandaDKP.Looting:CloseBidding(); dewdrop:Close() end,
	 order = 4 },
	
	canceltimer  = { type = 'execute', name = 'Cancel closing timer', desc = 'Cancel the countdown timer for close of bidding',
	 func = function() PandaDKP.Looting:CancelTimer(); dewdrop:Close() end,
	 order = 5 },
	
	cancelbidding = { type = 'execute', name = 'Cancel', desc = 'Cancel the open bidding and allow more items to be added',
	 func = function() PandaDKP.Looting:CancelBidding(); dewdrop:Close() end, order = 6 },
	
	announce = { type = 'execute', name = 'Announce', desc = 'Announce all item winners',
	 func = function() PandaDKP.Looting:AnnounceWinners(); dewdrop:Close() end, order = 7 },
	
	disenchantit = { type = 'execute', name = 'Disenchant', order = 2, desc = 'Disenchant the item',
	 func = function(id) PandaDKP.Looting:SetWinner(id, "Disenchant") end },
	
	guildbankit = { type = 'execute', name = 'Bank', order = 3, desc = 'Pass the item to the guild bank',
	 func = function(id) PandaDKP.Looting:SetWinner(id, "Bank") end },
	
	removeitem = { type = 'execute', name = 'Remove item', desc = 'Remove this item from the list',
	    func = function(v)
		      dewdrop:Close()
		      StaticPopupDialogs["PDKP_CONFIRM_REMOVEITEM"].UserData = v.id
		      StaticPopupDialogs["PDKP_CONFIRM_REMOVEITEM"].OnAccept = function(dialog) PandaDKP.Looting:RemoveItem(StaticPopupDialogs[dialog.which].UserData) end
		      StaticPopup_Show("PDKP_CONFIRM_REMOVEITEM", v.link)
		   end,
	    order = 1
	 }
}


StaticPopupDialogs["PDKP_CONFIRM_REMOVEITEM"] = {
   text = "Are you positive that you want to remove %s from the list?",
   button1 = "Yes",
   button2 = "No",
   OnAccept = nil, -- will be filled in by creater
   OnCancel = function() end,
   sound = "levelup2",
   timeout = 0,
   whileDead = 1,
   hideOnEscape = 1
}